package org.apache.ibatis.common.reflection;

import org.apache.ibatis.common.annotations.Param;
import org.apache.ibatis.config.binding.MapperMethod;
import org.apache.ibatis.sql.session.Configuration;
import org.apache.ibatis.sql.session.RowBounds;
import sun.plugin2.main.server.ResultHandler;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @Name ParamNameResolver
 * @Description: Mapper 映射接口参数名称解析器
 * 描述信息：它是一个参数名称解析器，用来按照顺序列出接口方法中的虚参，并对实参进行名称标注。
 * 它的主要方法有两个，构造器 ParamNameResolver 和成员方法 getNamedParams。
 * - 构造器 ParamNameResolver()：将目标方法的参数名称依次列举出来。在列举的过程中，如果某个参数存在 @Param 注解，则用注解 value 值替换参数名称。
 * - 成员方法 getNamedParams()：获取所有实参的参数名称。
 * 例如：UserDao 接口中的抽象方法
 * - List<User> queryUserBySchoolName(int id, String name, @Param("emailAddress") String email, int age, String schoolName);
 * @Version v1.0.0
 * @Author: lichao
 * @Date: 2024-04-28 16:56:50
 **/
public class ParamNameResolver {
    // 默认参数名称索引 {param1, param2, ... ...}
    private static final String GENERIC_NAME_PREFIX = "param";
    // 入参次序表 <入参次序，参数名称或者 @Param 注解的值>
    private final SortedMap<Integer, String> names;
    // 入参中是否含有 @Param 注解
    private boolean hasParamAnnotation;

    /**
     * 构造器
     *
     * @param config 配置信息
     * @param method 待解析的接口方法
     * @version v1.0.0
     * @author lichao
     * @date 2024-04-28 17:08:04
     */
    public ParamNameResolver(Configuration config, Method method) {
        // 所有参数的类型
        final Class<?>[] paramTypes = method.getParameterTypes();
        // 所有参数的注解
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final SortedMap<Integer, String> map = new TreeMap<>();
        int paramCount = paramAnnotations.length;
        // 循环处理各个参数
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
            // 跳过特别的参数
            if (isSpecialParamter(paramTypes[paramIndex])) continue;
            String paramName = null;
            // 对于注解参数，解析注解值
            for (Annotation annotation : paramAnnotations[paramIndex]) {
                // 找出参数的注解
                if (annotation instanceof Param) {
                    hasParamAnnotation = true;
                    paramName = ((Param) annotation).value();
                    break;
                }
            }
            // 对于普通参数，保留参数原有名称
            if (paramName == null) {
                if (config.isUseActualParamName()) paramName = getActualParamName(method, paramIndex);
                // 如果没有取到参数名称，则按照参数索引 index 命名
                if (paramName == null) paramName = String.valueOf(map.size());
            }
            map.put(paramIndex, paramName);
        }
        names = Collections.unmodifiableSortedMap(map);
    }

    private boolean isSpecialParamter(Class<?> clazz) {
        return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
    }

    private String getActualParamName(Method method, int paramIndex) {
        return ParamNameUtil.getParamNames(method).get(paramIndex);
    }

    /**
     * 获取被解析方法中的参数名称列表
     *
     * @return java.lang.String[] 结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-04-28 17:22:04
     */
    public String[] getNames() {
        return names.values().toArray(new String[0]);
    }

    /**
     * 将被解析方法中的参数名称列表与传入的 args 进行对应，返回对应关系。
     * 说明：
     * - 如果只有一个参数，直接返回参数；
     * - 如果存在多个参数，则返回对应关系。
     *
     * @param args 传入 args 列表
     * @return java.lang.Object 结果
     * @version v1.0.0
     * @author lichao
     * @date 2024-04-28 17:23:19
     */
    public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        if (args == null || paramCount == 0) return null;
        else if (!hasParamAnnotation && paramCount == 1) return args[names.firstKey()];
        else {
            final Map<String, Object> param = new MapperMethod.ParamMap<>();
            int index = 0;
            for (Map.Entry<Integer, String> entry : names.entrySet()) {
                // 注意：key 和 value 交换了位置
                param.put(entry.getValue(), args[entry.getKey()]);
                final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(index + 1);
                if (!names.containsKey(genericParamName))
                    param.put(genericParamName, args[entry.getKey()]);
                index++;
            }
            return param;
        }
    }


}

